import traceback
import uuid
from typing import Optional, List

from db import database
from logger import logger
from tools.webhook import WebHook

LOGGER = logger.CustomLogger()


def get_pr_event_status(id, event_name) -> Optional[database.PrEventForceStatus]:
    with database.session_scope() as session:
        event_info = session \
            .query(database.PrEventForceStatus) \
            .filter(
            database.PrEventForceStatus.id == id,
            database.PrEventForceStatus.event_name == event_name) \
            .first()
        return event_info


def get_pr_info(pr_number, repo_owner, repo_name) -> Optional[database.PullRequestInfos]:
    with database.session_scope() as session:
        pr_info = session \
            .query(database.PullRequestInfos) \
            .filter(
            database.PullRequestInfos.pr_number == pr_number,
            database.PullRequestInfos.repo_owner == repo_owner,
            database.PullRequestInfos.repo_name == repo_name) \
            .first()
        return pr_info


def get_or_create_pr_info_by_hook(hook: WebHook) -> Optional[database.PullRequestInfos]:
    try_time = 0
    while try_time < 3:
        try:
            with database.session_scope() as session:
                pr_info = get_pr_info(hook.pr_number, hook.repo_owner, hook.repo_name)
                if pr_info:
                    return pr_info
                pr_info_dict = {
                    "id": uuid.uuid4(),
                    "pr_number": hook.pr_number,
                    "repo_owner": hook.repo_owner,
                    "repo_name": hook.repo_name
                }
                new_pr_info = database.PullRequestInfos(**pr_info_dict)
                session.add(new_pr_info)
            return get_pr_info(hook.pr_number, hook.repo_owner, hook.repo_name)
        except Exception as e:
            LOGGER.error(f"[{hook.pr_number}] get_or_create_pr_info_by_hook failed: {e}")
            try_time += 1
    raise Exception(f"pr info {hook.pr_number, hook.repo_owner, hook.repo_name} not found")


def get_force_pass_status_by_hook(hook: WebHook, event_name) -> bool:
    pr_info = get_or_create_pr_info_by_hook(hook)
    if pr_info.force_pass:
        return True
    pr_event_status = get_pr_event_status(pr_info.id, event_name)
    return pr_event_status.force_pass if pr_event_status else False


def update_event_force_pass(hook: WebHook, event_name, force_account, force_pass=True):
    pr_info = get_or_create_pr_info_by_hook(hook)
    with database.session_scope() as session:
        event_info = get_pr_event_status(pr_info.id, event_name)
        if event_info:
            session \
                .query(database.PrEventForceStatus) \
                .filter(
                database.PrEventForceStatus.id == pr_info.id,
                database.PrEventForceStatus.event_name == event_name) \
                .update({"force_pass": force_pass})
        else:
            event_status_dict = {
                "id": pr_info.id,
                "event_name": event_name,
                "force_pass": force_pass,
                "force_account": force_account
            }
            event_status = database.PrEventForceStatus(**event_status_dict)
            session.add(event_status)


def get_code_owners(id=None, hook=None) -> List[database.PrCodeOwners]:
    with database.session_scope() as session:
        if not id:
            id = get_or_create_pr_info_by_hook(hook).id
        owners = session \
            .query(database.PrCodeOwners) \
            .filter(
            database.PrCodeOwners.id == id) \
            .all()
        return owners


def update_pr_code_owner(hook: WebHook, owner_name, delete=False):
    pr_info = get_or_create_pr_info_by_hook(hook)
    try:
        with database.session_scope() as session:
            owner_dict = {
                "id": pr_info.id,
                "owner_name": owner_name
            }
            if delete:
                owner = session.query(database.PrCodeOwners).filter_by(**owner_dict).first()
                if owner:
                    session.delete(owner)
            else:
                new_owner = database.PrCodeOwners(**owner_dict)
                session.add(new_owner)
    except Exception as e:
        LOGGER.error(f"[{hook.pr_number}] update_pr_code_owner(delete: {delete}) failed, details: {str(e)}, "
                     f"traceback:\n{traceback.format_exc()}")
        raise Exception(f"update_pr_code_owner(delete: {delete}) failed")


def update_pr_code_owners(hook: WebHook, code_owners):
    pr_info = get_or_create_pr_info_by_hook(hook)
    with database.session_scope():
        old_owners = get_code_owners(id=pr_info.id)
        old_owners_set = {x.owner_name for x in old_owners}
        new_owners_set = set(code_owners)
        added = new_owners_set - old_owners_set
        deleted = old_owners_set - new_owners_set
        for code_owner in added:
            update_pr_code_owner(hook, code_owner)
        for code_owner in deleted:
            update_pr_code_owner(hook, code_owner, True)
        return added, deleted


def update_code_owner_pass(hook: WebHook, code_owner, passed=True):
    pr_info = get_or_create_pr_info_by_hook(hook)
    result = False
    try:
        with database.session_scope() as session:
            owner_dict = {
                "id": pr_info.id,
                "owner_name": code_owner
            }
            owner = session.query(database.PrCodeOwners).filter_by(**owner_dict).first()
            if owner:
                update_dict = {"passed": passed}
                result = passed != owner.passed
                session.query(database.PrCodeOwners).filter_by(**owner_dict).update(update_dict)
                return result
    except Exception as e:
        LOGGER.error(f"[{hook.pr_number}] update_pr_code_owner pass failed, details: {str(e)}, "
                     f"traceback:\n{traceback.format_exc()}")
        raise Exception(f"update_pr_code_owner pass failed")
    return result
